package org.xx.armory.commons;

import java.io.Serializable;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.commons.lang3.math.NumberUtils.toInt;
import static org.xx.armory.commons.Validators.greaterThanOrEqual;
import static org.xx.armory.commons.Validators.notBlank;

/**
 * 软件或者组件的版本。
 */
public final class Version
        implements Comparable<Version>, Serializable {
    private static final long serialVersionUID = 1L;
    private static Pattern VERSION = Pattern.compile("^(\\d{1,5})\\.(\\d{1,5})(\\.(\\d{1,5})(\\.(\\d{1,5}))?)?$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
    private final int major;
    private final int minor;
    private final int build;
    private final int revision;

    /**
     * 构造版本号对象。
     *
     * @param major
     *         主版本号。
     * @param minor
     *         次版本号。
     * @param build
     *         构建序号。
     * @param revision
     *         修订号。
     * @throws IllegalArgumentException
     *         如果至少一个字段值小于{@literal 0}。
     */
    public Version(
            int major,
            int minor,
            int build,
            int revision
    ) {
        this.major = greaterThanOrEqual(major, "major", 0);
        this.minor = greaterThanOrEqual(minor, "minor", 0);
        this.build = greaterThanOrEqual(build, "build", 0);
        this.revision = greaterThanOrEqual(revision, "revision", 0);
    }

    /**
     * 构造版本号对象，修订号为0。
     *
     * @param major
     *         主版本号。
     * @param minor
     *         次版本号。
     * @param build
     *         构建序号。
     * @throws IllegalArgumentException
     *         如果至少一个字段值小于{@literal 0}。
     */
    public Version(
            int major,
            int minor,
            int build
    ) {
        this(major, minor, build, 0);
    }

    /**
     * 构造版本号对象，构建序号和修订号为0。
     *
     * @param major
     *         主版本号。
     * @param minor
     *         次版本号。
     * @throws IllegalArgumentException
     *         如果至少一个字段值小于{@literal 0}。
     */
    public Version(
            int major,
            int minor
    ) {
        this(major, minor, 0, 0);
    }

    /**
     * 构建版本号对象。
     *
     * @param s
     *         表示版本号的字符串，至少需要包含主版本号和次版本号，自动去掉首尾空格。
     * @throws IllegalArgumentException
     *         如果参数{@code s}是{@code null}或者只包含空白字符，或者参数{@code s}不表示合法的版本号。
     */
    public Version(
            String s
    ) {
        s = notBlank(s, "s").trim();
        final Matcher m = VERSION.matcher(s);
        if (m.find()) {
            this.major = toInt(m.group(1));
            this.minor = toInt(m.group(2));
            if (m.groupCount() == 4) {
                this.build = toInt(m.group(4));
                this.revision = 0;
            } else if (m.groupCount() == 6) {
                this.build = toInt(m.group(4));
                this.revision = toInt(m.group(6));
            } else {
                this.build = 0;
                this.revision = 0;
            }
        } else {
            throw new IllegalArgumentException("illegal argument \"s\": " + s);
        }
    }

    /**
     * 获取主版本号。
     *
     * @return 主版本号。
     */
    public int getMajor() {
        return major;
    }

    /**
     * 获取次版本号。
     *
     * @return 次版本号。
     */
    public int getMinor() {
        return minor;
    }

    /**
     * 获取构建序号（通常对应源码的版本号）。
     *
     * @return 构建序号。
     */
    public int getBuild() {
        return build;
    }

    /**
     * 获取修订号。
     *
     * @return 修订号。
     */
    public int getRevision() {
        return revision;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int compareTo(
            Version other
    ) {
        if (other == null) {
            return 1;
        }

        final int c1 = Integer.compare(this.major, other.major);
        final int c2 = Integer.compare(this.minor, other.minor);
        final int c3 = Integer.compare(this.build, other.build);
        final int c4 = Integer.compare(this.revision, other.revision);

        if (c1 != 0) return c1;
        if (c2 != 0) return c2;
        if (c3 != 0) return c3;
        else return c4;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Objects.hash(major, minor, build, revision);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(
            Object other
    ) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        final Version version = (Version) other;
        return this.major == version.major &&
                this.minor == version.minor &&
                this.build == version.build &&
                this.revision == version.revision;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        return this.major + "." + this.minor + "." + this.build + "." + this.revision;
    }
}
